package com.pointcircle.core.web;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.LinkedList;
import java.util.List;

import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import com.pointcircle.core.ApplicationContextHolder;
import com.pointcircle.core.IRepository;
import com.pointcircle.core.JpaBaseEntity;

public interface IRest<T extends JpaBaseEntity<ID>, ID extends Serializable, 
	EV extends EntityVo<T, ID>> {

	IRepository<T, ID> getRepository();
	
	default RestResponseElement createRestResponseElement(T entity) {
		RestResponseElement response = new RestResponseElement();
		response.setElement(entity == null ? null : this.generateEntityVo(entity));
		response.setSuccess(true);
		return response;
	}
	
	default RestResponseList createRestResponseList(List<T> entities) {
		RestResponseList response = new RestResponseList();
		response.setSuccess(true);
		List<EV> list = new LinkedList<EV>();
		for(T entity : entities) {
			list.add(this.generateEntityVo(entity));
		}
		response.setList(list);
		return response;
	}

	default RestResponsePageList createRestResponsePageList(
			List<T> entities, int page, int size, int totalPages, int totalElements) {
		RestResponsePageList response = new RestResponsePageList();
		List<EV> list = new LinkedList<EV>();
		for(T entity : entities) {
			list.add(this.generateEntityVo(entity));
		}
		response.setList(list);
		response.setPage(page);
		response.setSize(size);
		response.setTotalPages(totalPages);
		response.setTotalElements(totalElements);
		response.setSuccess(true);
		return response;
	}
	
	default RestResponsePageList createRestResponsePageList(
			Pageable pageable, Page<T> entities) {
		return this.createRestResponsePageList(
			entities.getContent(), pageable.getPageNumber() + 1, pageable.getPageSize(), 
			entities.getTotalPages(), (int) entities.getTotalElements()
		);
	}
	
	default Class<?> getEntityClass() {
		ResolvableType thisResolvableType = ResolvableType.forClass(this.getClass());
		ResolvableType[] interfaceTypes = thisResolvableType.getInterfaces();
		ResolvableType entityResolvableType = null;
		for(ResolvableType interfaceType : interfaceTypes) {
			if(ResolvableType.forClass(IRest.class).isAssignableFrom(interfaceType)) {
				ResolvableType[] genericTypes = interfaceType.getGenerics();
				for(ResolvableType genericType : genericTypes) {
					if(ResolvableType.forClass(JpaBaseEntity.class).isAssignableFrom(genericType)) {
						entityResolvableType = genericType;
					}
				}
				break;
			}
		}
		Class<?> entityClass = entityResolvableType.getRawClass();
		return entityClass;
	}
	
	default Class<?> getEntityVoClass() {
		ResolvableType thisResolvableType = ResolvableType.forClass(this.getClass());
		ResolvableType[] interfaceTypes = thisResolvableType.getInterfaces();
		ResolvableType entityVoResolvableType = null;
		for(ResolvableType interfaceType : interfaceTypes) {
			if(ResolvableType.forClass(IRest.class).isAssignableFrom(interfaceType)) {
				ResolvableType[] genericTypes = interfaceType.getGenerics();
				for(ResolvableType genericType : genericTypes) {
					if(ResolvableType.forClass(EntityVo.class).isAssignableFrom(genericType)) {
						entityVoResolvableType = genericType;
					}
				}
				break;
			}
		}
		Class<?> entityVoClass = entityVoResolvableType.getRawClass();
		return entityVoClass;
	}
	
	default T generateEntity() throws InstantiationException, IllegalAccessException {
		Class<?> entityClass = this.getEntityClass();
		@SuppressWarnings("unchecked")
		T entity = (T) entityClass.newInstance();
		return entity;
	}
	
	default EV generateEntityVo(T entity) {
		try {
			Class<?> entityClass = this.getEntityClass();
			Class<?> entityVoClass = this.getEntityVoClass();
			EV responseVo = null;
			try {
				@SuppressWarnings("unchecked")
				Constructor<EV> constructor = (Constructor<EV>) entityVoClass.getDeclaredConstructor(this.getClass(), entityClass);
				responseVo = (EV) constructor.newInstance(this, entity);
			} catch(NoSuchMethodException ex) {
				@SuppressWarnings("unchecked")
				Constructor<EV> constructor = (Constructor<EV>) entityVoClass.getConstructor(entityClass);
				responseVo = (EV) constructor.newInstance(entity);
			}
			return responseVo;
		} catch(Exception e) {
			throw new RestException(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	default IRest<T, ID, EV> getSelfComponent() {
		return (IRest<T, ID, EV>) ApplicationContextHolder.getBean(this.getClass());
	}
}
